#include "SQLStr.h"
#include "../common/CppMemPool.h"
#include "../common/UtilFuncT.h"
#include "../lang/Primitive.h"
#include "../util/DateTime.h"
#include "../exceptions/ArgumentException.h"
#include "sqlite/SQLiteDatabase.h"
#include "SQLStrException.h"

using namespace cppflib::lang;
using namespace cppflib::util;
using namespace cppflib::exceptions;
using namespace cppflib::database::sqlite;

namespace cppflib
{

namespace database
{


/**
 *  Print value to CFString based on data type 
 */
PRIVATE void __AppendValueType(CFString &sb, BaseObject *val)
{
   if (val == NULL) {
      throw ArgumentException(_S("Value type can't be NULL"), -1);
   }
   else if (cppflib::InstanceOf<CFString>(val)) {
      /*
       *  Use a single quote to wrap around a string type.
       *  At the same, append one more "'" after each single quote
       *  so as to "escape" this character.
       */
      CFString *strVal = static_cast<CFString*>(val);
      if (strVal->Find(_S('\'')) >= 0) {
         CFString newS(*strVal);
         newS.Replace(_S("'"), _S("''"));
         sb.AppendFormat(_S("'%s'"), newS.ToString());
      }
      else {
         sb.AppendFormat(_S("'%s'"), strVal->ToString());
      }
   }
   else if (cppflib::InstanceOf<Int32>(val)) {
      sb.AppendFormat(_S("%d"), static_cast<Int32*>(val)->Value());
   } 
   else if (cppflib::InstanceOf<Int64>(val)) {
      sb.AppendFormat(_S("%lld"), static_cast<Int64*>(val)->Value());
   } 
   else if (cppflib::InstanceOf<UInt32>(val)) {
      sb.AppendFormat(_S("%u"), static_cast<UInt32*>(val)->Value());
   } 
   else if (cppflib::InstanceOf<UInt64>(val)) {
      sb.AppendFormat(_S("%llu"), static_cast<UInt64*>(val)->Value());
   }
   else if (cppflib::InstanceOf<Float>(val)) {
      sb.AppendFormat(_S("%f"), static_cast<Float*>(val)->Value());
   }
   else if (cppflib::InstanceOf<Double>(val)) {
      sb.AppendFormat(_S("%f"), static_cast<Double*>(val)->Value());
   }
   else if (cppflib::InstanceOf<DateTime>(val)) {
      DateTime *dt = static_cast<DateTime*>(val);
      sb.AppendFormat(_S("'%04d-%02d-%02d %02d:%02d:%02d'"), 
                          dt->Year(), dt->Month(), dt->Day(), dt->Hour(), dt->Min(), dt->Sec());
   }
   else if (cppflib::InstanceOf<SQLStr::PlaceHolder>(val)) {
      sb += static_cast<SQLStr::PlaceHolder*>(val)->ToString();
   }
   else {
      throw SQLStrException(_S("Unknown type when appending value of object"));
   }
}


/******************* SQLCondClause ***********************/

/**
 *  @param [in] cond1 -- a SqlCondClause or a SqlSimpCond
 *  @param [in] op -- operation string, e.g. "=", "<", ">"
 *  @param [in] cond2 -- a SqlCondClause or a SqlSimpCond
 */
SQLStr::SQLCondClause::SQLCondClause(SQLBaseClause *cond1, const CFString &op, SQLBaseClause *cond2)
{
   this->cond1 = cond1;
   cppmempool::Retain(this->cond1);
   this->op = op;
   this->cond2 = cond2;
   cppmempool::Retain(this->cond2);
}

SQLStr::SQLCondClause::~SQLCondClause()
{
   cppmempool::Release(this->cond1);
   cppmempool::Release(this->cond2);   
}

/**
 *  Expand the condition object
 */
void SQLStr::SQLCondClause::ProcessCond(CFString &sb, SQLBaseClause *cond)
{
   if (cppflib::InstanceOf<SQLCondClause>(cond)) {
      static_cast<SQLCondClause*>(cond)->GenString(sb);
   }
   else if (cppflib::InstanceOf<SQLSimpCond>(cond)) {
      static_cast<SQLSimpCond*>(cond)->GenString(sb);
   }
}

/**
 *  Generate string according to paramters passed in 
 *
 *  @param [in, out] sb -- string object to receive the resultant string
 */
void SQLStr::SQLCondClause::GenString(CFString &sb)
{
   sb += _S("(");
   ProcessCond(sb, this->cond1);
   sb.AppendFormat(_S(" %s "), op.ToString());
   ProcessCond(sb, this->cond2);
   sb += _S(")");
}

/******************* SQLSimpCond ***********************/

SQLStr::SQLSimpCond::SQLSimpCond(const CFString &op)
{
   this->op = op;
   this->value = NULL;
}

SQLStr::SQLSimpCond::SQLSimpCond(const CFString &name, const CFString &op, BaseObject *value)
{
   this->name = name;
   this->op = op;
   this->value = value;
   cppmempool::Retain(value);
}

SQLStr::SQLSimpCond::~SQLSimpCond()
{
   cppmempool::Release(value);
}

/**
 *  Generate string according to paramters passed in 
 *
 *  @param [in, out] sb -- string object to receive the resultant string
 */
void SQLStr::SQLSimpCond::GenString(CFString &sb)
{
   if (this->value == NULL) {
      sb.AppendFormat(_S(" %s "), this->op.ToString());
   }
   else {
      sb.AppendFormat(_S("%s %s "), this->name.ToString(), this->op.ToString());
      __AppendValueType(sb, this->value);
   }
}

/******************* PlaceHolder ***********************/

SQLStr::PlaceHolder::PlaceHolder(const CFString &name)
{
   // add a prefix
   this->placeHolderName = _S("@p_") + name;
}

/**
 * Get the underlying place holder name
 */
const CFString& SQLStr::PlaceHolder::GetName() const
{
   return this->placeHolderName;
}

pcwstr_t SQLStr::PlaceHolder::ToString(void) const
{
   return static_cast<pcwstr_t>(this->placeHolderName);
}

/******************* SQLStr ***********************/

/**
 *  Generate a "Create table" SQL query
 * 
 *  @param [in] tabName -- table name
 *  @param [in] colsAndTypes -- column name and data type passed in this fashion
 *                    "colName1", "colDataType1", "colName2", "colDataType2", ...
 *
 *  @return a pointer to a newly allocated string
 */
const CFString *SQLStr::GenCreateTable(const CFString &tabName, collections::ListCppObjT<CFString> &colsAndTypes)
{
   CFString *sb = new CFString(static_cast<pcwstr_t>(NULL), 0, 256);

   sb->Format(_S("CREATE TABLE %s ("), static_cast<pcwstr_t>(tabName)); 
   for(int i = 0; i < static_cast<int>(colsAndTypes.GetCount()); i += 2) {
      
      sb->AppendFormat(_S("%s %s,"), colsAndTypes.Get(i)->ToString(), colsAndTypes.Get(i + 1)->ToString());
   }
   sb->Delete(sb->GetLength() - 1, 1); // remove the last comma
   *sb += _S(")"); 

   return sb;
}

/**
 *  Generate a "Drop table" SQL query
 */
const CFString *SQLStr::GenDropTable(const CFString &tabName)
{
   // Adding "IF EXISTS" will not generate error if table does not exists
   CFString *sb = new CFString(static_cast<pcwstr_t>(NULL), 0, 64);
   sb->Format(_S("DROP TABLE IF EXISTS %s"), static_cast<pcwstr_t>(tabName));
   return sb;
}

/**
 *  Generate a "SELECT" SQL
 * 
 *  @param [in] tabName -- table name
 *  @param [in] whereClause -- where clause (may be empty)
 *  @param [in] cols -- column name (each one is a String)
 */
const CFString *SQLStr::GenSelect(const CFString &tabName, collections::ListCppObjT<CFString> &cols, const CFString &whereClause, 
                                  const CFString &groupBy, const CFString &having, const CFString &orderBy)
{
   CFString *sb = new CFString(static_cast<pcwstr_t>(NULL), 0, 256);

   *sb += _S("SELECT ");
   for (int i = 0 ; i < static_cast<int>(cols.GetCount()); ++i) {
      sb->AppendFormat(_S("%s,"), cols.Get(i)->ToString());
   }

   sb->Delete(sb->GetLength() - 1, 1); // remove the last comma
   sb->AppendFormat(_S(" FROM %s"), static_cast<pcwstr_t>(tabName));
   if (!whereClause.IsEmpty())
      sb->AppendFormat(_S(" WHERE %s"), static_cast<pcwstr_t>(whereClause));

   if (!groupBy.IsEmpty())
      sb->AppendFormat(_S(" GROUP BY %s"), static_cast<pcwstr_t>(groupBy));

   if (!having.IsEmpty())
      sb->AppendFormat(_S(" HAVING %s"), static_cast<pcwstr_t>(having));

   if (!orderBy.IsEmpty())
      sb->AppendFormat(_S(" ORDER BY %s"), static_cast<pcwstr_t>(orderBy));

   return sb;
}

/**
 *  Generate a "SELECT" SQL, which also selects the hidden 'rowid' column
 *
 *  @param [in] tabName -- table name
 *  @param [in] cols -- column name (each one is a String)
 *  @param [in] whereClause -- where clause (may be empty)
 */
const CFString *SQLStr::GenSelectWithRowid(const CFString &tabName, collections::ListCppObjT<CFString> &cols,
                                           const CFString &whereClause)
{
   collections::ListCppObjT<CFString> newCols;
   for (int i = 0; i < static_cast<int>(cols.GetCount()); ++i) {
      newCols.AddTail(cols.Get(i));
   }

   // add rowid at the end
   CFString * rowidCol = new CFString(SQLiteDatabase::GetConstName(SQLiteDatabase::ROWID));
   newCols.AddTail(rowidCol);
   cppmempool::Release(rowidCol);

   return GenSelect(tabName, newCols, whereClause, CFString::Empty(), CFString::Empty(), CFString::Empty()); 
   
}

/**
 *  Generate a "INSERT" SQL
 * 
 *  @param [in] tabName -- table name
 *  @param [in] colsAndValues -- columns and values passed in this fashion
 *                    "colName1", "colValue1", "colName2", "colValue2", ...
 */
const CFString *SQLStr::GenInsert(const CFString &tabName, collections::ListCppObjT<BaseObject> &colsAndValues)
{
   CFString *sb = new CFString(static_cast<pcwstr_t>(NULL), 0, 256);
   CFString sb2 = CFString(static_cast<pcwstr_t>(NULL), 0, 128);

   sb->AppendFormat(_S("INSERT INTO %s ("), static_cast<pcwstr_t>(tabName));
   sb2 += _S(" VALUES (");
   for(int i = 0; i < static_cast<int>(colsAndValues.GetCount()); i += 2) {
      sb->AppendFormat(_S("%s,"), colsAndValues.Get(i)->ToString());
      __AppendValueType(sb2, colsAndValues.Get(i + 1));
      sb2 += _S(",");
   }
          
   sb->Delete(sb->GetLength() - 1, 1); // remove the last comma
   sb2.Delete(sb2.GetLength() - 1, 1); // remove the last comma
   *sb += _S(")");
   sb2 += _S(")");
   *sb += sb2;

   return sb;
}

/**
 *  Generate a "UPDATE" SQL
 * 
 *  @param [in] tabName -- table name
 *  @param [in] whereClause -- where clause (may be empty)
 *  @param [in] colsAndValues -- columns and values passed in this fashion
 *                    "colName1", "colValue1", "colName2", "colValue2", ...
 */
const CFString *SQLStr::GenUpdate(const CFString &tabName, collections::ListCppObjT<BaseObject> &colsAndValues, const CFString &whereClause)
{
   CFString *sb = new CFString(static_cast<pcwstr_t>(NULL), 0, 256);

   sb->AppendFormat(_S("UPDATE %s SET "), static_cast<pcwstr_t>(tabName));
   for(int i = 0; i < static_cast<int>(colsAndValues.GetCount()); i += 2) {
      sb->AppendFormat(_S("%s = "), colsAndValues.Get(i)->ToString());
      __AppendValueType(*sb, colsAndValues.Get(i + 1));
      *sb += _S(",");
   }

   sb->Delete(sb->GetLength() - 1, 1); // remove the last comma

   if (!whereClause.IsEmpty())
      sb->AppendFormat(_S(" WHERE %s"), static_cast<pcwstr_t>(whereClause));

   return sb;
}

/**
 *  Generate a "DELETE" SQL
 * 
 *  @param [in] tabName -- table name
 *  @param [in] whereClause -- where clause (may be empty)
 */
const CFString *SQLStr::GenDelete(const CFString &tabName, const CFString &whereClause)
{
   CFString *sb = new CFString(static_cast<pcwstr_t>(NULL), 0, 256);

   sb->AppendFormat(_S("DELETE FROM %s"), static_cast<pcwstr_t>(tabName));
   if (!whereClause.IsEmpty())
      sb->AppendFormat(_S(" WHERE %s"), static_cast<pcwstr_t>(whereClause));

   return sb;
}

/**
 *  Generate a "WHERE" clause
 * 
 *  @param [in] values -- each element is a SqlCondClause or a SqlSimpCond or a String
 */
const CFString *SQLStr::GenWhere(collections::ListCppObjT<SQLBaseClause> &values)
{
   CFString *sb = new CFString(static_cast<pcwstr_t>(NULL), 0, 256);

   for(int i = 0; i < static_cast<int>(values.GetCount()); ++i) {
      BaseObject *val = values.Get(i);
      if (cppflib::InstanceOf<SQLCondClause>(val)) {
         static_cast<SQLCondClause*>(val)->GenString(*sb);
      }
      else if (cppflib::InstanceOf<SQLSimpCond>(val)) {
         static_cast<SQLSimpCond*>(val)->GenString(*sb);
      }
   }

   return sb;
}


} // end of namespace database

} // end of namespace cppflib

